﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Diagnostics;

namespace Microsoft.Services.NuGet.Server
{
    internal static class QueryableExtensions
    {
        private static readonly bool _isRewritingRequired = IsRewritingRequired();

        /// <summary>
        /// Replacing closures with constant values is required only when executing in partial trust and the NuGet assembly is GACed.
        /// </summary>
        private static bool IsRewritingRequired()
        {
            AppDomain appDomain = AppDomain.CurrentDomain;
            Assembly assembly = typeof(QueryableExtensions).Assembly; ;
            return appDomain.IsHomogenous && !appDomain.IsFullyTrusted && assembly.IsFullyTrusted;
        }

        /// <summary>
        /// The purpose of this method is to mitigate a partial trust issue. We expose
        /// EnumerableQuery (wrapping an enumrable in an IQueryable) throughout the codebase
        /// and expression compilation doesn't work in some cases. See SafeEnumerableQuery for more details.
        /// </summary>
        internal static IQueryable<T> AsSafeQueryable<T>(this IEnumerable<T> source)
        {
            return AsSafeQueryable(source, rewriteQuery: _isRewritingRequired);
        }

        internal static IQueryable<T> AsSafeQueryable<T>(this IEnumerable<T> source, bool rewriteQuery)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            if (rewriteQuery)
            {
                return new SafeEnumerableQuery<T>(source);
            }
            // AsQueryable returns the original source if it is already a IQueryable<T>. 
            return source.AsQueryable();
        }

    }

    /// <summary>
    /// There are some seurity issues around evaluating queries over EnumerableQuery with clousures.
    /// The compiler generates an internal type that can't be causes expression compilation to fail, when the 
    /// calling assembly is in the GAC and is SecurityTransparent. We wrap the underlying enumerable query and 
    /// then remove all compiler generated closures from the expression before compilation.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Type is an IQueryable and by convention should end with the term Query")]
    internal class SafeEnumerableQuery<T> : IQueryable<T>, IQueryProvider, IOrderedQueryable<T>
    {
        private readonly IQueryable _enumerableQuery;
        private readonly Expression _expression;

        public SafeEnumerableQuery(IEnumerable<T> enumerable)
        {
            if (enumerable == null)
            {
                throw new ArgumentNullException("enumerable");
            }
            _enumerableQuery = enumerable.AsQueryable();
            _expression = Expression.Constant(this);
        }

        private SafeEnumerableQuery(IQueryable enumerableQuery, Expression expression)
        {
            _enumerableQuery = enumerableQuery;
            _expression = expression;
        }

        public IEnumerator<T> GetEnumerator()
        {
            // Create the new query and return the enumerator
            return InnerProvider.CreateQuery<T>(InnerExpression).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public Type ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        public Expression Expression
        {
            get
            {
                return _expression;
            }
        }

        public IQueryProvider Provider
        {
            get
            {
                return this;
            }
        }

        private IQueryProvider InnerProvider
        {
            get
            {
                return _enumerableQuery.Provider;
            }
        }

        internal Expression InnerExpression
        {
            get
            {
                // Rewrite all closures to use their values
                return GetInnerExpression(Expression);
            }
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return (IQueryable<TElement>)CreateQuery(typeof(TElement), expression);
        }

        public IQueryable CreateQuery(Expression expression)
        {
            // Copied logic from EnumerableQuery
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            Type elementType = QueryableUtility.FindGenericType(typeof(IQueryable<>), expression.Type);

            if (elementType == null)
            {
                throw new ArgumentException(String.Empty, "expression");
            }

            return CreateQuery(elementType, expression);
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return InnerProvider.Execute<TResult>(GetInnerExpression(expression));
        }

        public object Execute(Expression expression)
        {
            return InnerProvider.Execute(GetInnerExpression(expression));
        }

        private Expression GetInnerExpression(Expression expression)
        {
            // First replace the this IQueryable with the enumerable query
            expression = QueryableUtility.ReplaceQueryableExpression(_enumerableQuery, expression);

            // Evaluate the closure variables
            return new ClosureEvaluator().Visit(expression);
        }

        private IQueryable CreateQuery(Type elementType, Expression expression)
        {
            var queryType = typeof(SafeEnumerableQuery<>).MakeGenericType(elementType);
            var ctor = queryType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();

            return (IQueryable)ctor.Invoke(new object[] { _enumerableQuery, expression });
        }

        public override string ToString()
        {
            return _enumerableQuery.ToString();
        }
    }

    internal static class QueryableUtility
    {
        private static readonly string[] _orderMethods = new[] { "OrderBy", "ThenBy", "OrderByDescending", "ThenByDescending" };
        private static readonly MethodInfo[] _methods = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static);

        private static MethodInfo GetQueryableMethod(Expression expression)
        {
            if (expression.NodeType == ExpressionType.Call)
            {
                var call = (MethodCallExpression)expression;
                if (call.Method.IsStatic && call.Method.DeclaringType == typeof(Queryable))
                {
                    return call.Method.GetGenericMethodDefinition();
                }
            }
            return null;
        }

        public static bool IsQueryableMethod(Expression expression, string method)
        {
            return _methods.Where(m => m.Name == method).Contains(GetQueryableMethod(expression));
        }

        public static bool IsOrderingMethod(Expression expression)
        {
            return _orderMethods.Any(method => IsQueryableMethod(expression, method));
        }

        public static Expression ReplaceQueryableExpression(IQueryable query, Expression expression)
        {
            return new ExpressionRewriter(query).Visit(expression);
        }

        public static Type FindGenericType(Type definition, Type type)
        {
            while ((type != null) && (type != typeof(object)))
            {
                if (type.IsGenericType && (type.GetGenericTypeDefinition() == definition))
                {
                    return type;
                }
                if (definition.IsInterface)
                {
                    foreach (Type interfaceType in type.GetInterfaces())
                    {
                        Type genericType = FindGenericType(definition, interfaceType);
                        if (genericType != null)
                        {
                            return genericType;
                        }
                    }
                }
                type = type.BaseType;
            }
            return null;
        }

        private class ExpressionRewriter : ExpressionVisitor
        {
            private readonly IQueryable _query;

            public ExpressionRewriter(IQueryable query)
            {
                _query = query;
            }

            protected override Expression VisitConstant(ConstantExpression node)
            {
                // Replace the query at the root of the expression
                if (typeof(IQueryable).IsAssignableFrom(node.Type))
                {
                    return _query.Expression;
                }
                return base.VisitConstant(node);
            }
        }
    }

    internal class ClosureEvaluator : ExpressionVisitor
    {
        // For unit testing. We want to circumvent the assembly check during unit testing since
        // closures will be generated in that assembly.
        private bool _checkAssemly;

        internal ClosureEvaluator(bool checkAssembly = true)
        {
            _checkAssemly = checkAssembly;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (IsGeneratedClosureMember(node))
            {
                var constantExpression = (ConstantExpression)node.Expression;
                var fieldInfo = (FieldInfo)node.Member;
                // Evaluate the closure member
                return Expression.Constant(GetValue(node, fieldInfo, constantExpression.Value));
            }
            return base.VisitMember(node);
        }

        private object GetValue(MemberExpression node, FieldInfo fieldInfo, object obj)
        {
            if (_checkAssemly)
            {
                Type parentType = node.Expression.Type.DeclaringType;
                Debug.Assert(parentType != null, "Not in a compiler generated closure type");

                // Since the closure class is private sealed, we're going to look for an eval method on that class
                // where it's ok to look up field info.
                MethodInfo evalMethodInfo = parentType.GetMethod("Eval", BindingFlags.NonPublic | BindingFlags.Static);
                Debug.Assert(evalMethodInfo != null, "Eval method cannot be found. Please add and Eval(FieldInfo info, object value) to " + parentType.FullName);

                // Invoke that method
                return evalMethodInfo.Invoke(null, new object[] { fieldInfo, obj });
            }

            // This only happens in the unit test
            return fieldInfo.GetValue(obj);
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            return base.VisitConstant(node);
        }

        private bool IsGeneratedClosureMember(MemberExpression node)
        {
            // Closure types are internal classes that are compiler generated in our own assembly
            return node.Expression != null &&
                   node.Member != null &&
                   node.Expression.NodeType == ExpressionType.Constant &&
                   node.Member.MemberType == MemberTypes.Field &&
                   !node.Expression.Type.IsVisible &&
                   CheckAssembly(node.Member) &&
                   IsCompilerGenerated(node.Expression.Type);
        }

        private bool CheckAssembly(MemberInfo member)
        {
            if (_checkAssemly)
            {
                // Make sure we're in our assembly
                return member.DeclaringType.Assembly == typeof(ClosureEvaluator).Assembly;
            }
            // This is only the case for unit tests
            return true;
        }

        private static bool IsCompilerGenerated(Type type)
        {
            return type.GetCustomAttributes(inherit: true)
                       .OfType<CompilerGeneratedAttribute>()
                       .Any();
        }
    }
}